<!DOCTYPE html>
<html class="has-navbar-fixed-top">
<head>
    <meta charset="utf-8">
<title>异常处理 - wanzixin</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css">


<link href="/zh-cn/Study/Java/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/" rel="alternate" hreflang="zh-CN" />
    


<meta name="description" content="">





    <meta name="description" content="程序运行的时候，经常会发生各种错误。本章我们讨论如何在Java程序中处理各种异常情况。">
<meta property="og:type" content="article">
<meta property="og:title" content="异常处理">
<meta property="og:url" content="https://wanzixin.github.io/Study/Java/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/index.html">
<meta property="og:site_name" content="wanzixin">
<meta property="og:description" content="程序运行的时候，经常会发生各种错误。本章我们讨论如何在Java程序中处理各种异常情况。">
<meta property="og:locale" content="en_US">
<meta property="article:published_time" content="2021-04-27T06:31:29.000Z">
<meta property="article:modified_time" content="2021-05-30T13:32:44.610Z">
<meta property="article:author" content="wanzixin">
<meta name="twitter:card" content="summary">





<link rel="icon" href="/favicon.png">


<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Ovo|Source+Code+Pro">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/css/justifiedGallery.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">


<link rel="stylesheet" href="/css/style.css">


<script defer src="//use.fontawesome.com/releases/v5.0.8/js/all.js"></script>


    
    
    
    
    
    
    
    
    
    

    


<meta name="generator" content="Hexo 5.4.0"></head>
<body>
    
<nav class="navbar is-transparent is-fixed-top navbar-main" role="navigation" aria-label="main navigation">
    <div class="container">
        <div class="navbar-brand">
            <a class="navbar-item navbar-logo" href="/">
                
                    
                    wanzixin
                    
                
            </a>
            <div class="navbar-burger">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>
        
        <div class="navbar-menu navbar-start">
            
            <a class="navbar-item "
               href="/archives">Archives</a>
            
            <a class="navbar-item "
               href="/categories">Categories</a>
            
            <a class="navbar-item "
               href="/categories/Diary">Diary</a>
            
            <a class="navbar-item "
               href="/categories/Gallery">Gallery</a>
            
            <a class="navbar-item "
               href="/categories/Study">Study</a>
            
            <a class="navbar-item "
               href="/categories/Item">Item</a>
            
            <a class="navbar-item "
               href="/about">About</a>
            
        </div>
        
        <div class="navbar-menu navbar-end">
            
            <a class="navbar-item search" title="Search" href="javascript:;">
                <i class="fas fa-search"></i>
            </a>
            
            
            <div class="navbar-item is-hoverable has-dropdown is-hidden-mobile is-hidden-tablet-only toc">
                <a class="navbar-item" title="Table of Contents">
                    <i class="fa fa-list"></i>
                </a>
                <div class="navbar-dropdown is-right">
                    
                    
                    
                    
                    <a class="navbar-item" href="#Java的异常">1&nbsp;&nbsp;<b>Java的异常</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#捕获异常">2&nbsp;&nbsp;<b>捕获异常</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#多catch语句">2.1&nbsp;&nbsp;多catch语句</a>
                    
                    
                    
                    <a class="navbar-item" href="#finally语句">2.2&nbsp;&nbsp;finally语句</a>
                    
                    
                    
                    <a class="navbar-item" href="#捕获多种异常">2.3&nbsp;&nbsp;捕获多种异常</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#抛出异常">3&nbsp;&nbsp;<b>抛出异常</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#异常的传播">3.1&nbsp;&nbsp;异常的传播</a>
                    
                    
                    
                    <a class="navbar-item" href="#抛出异常-1">3.2&nbsp;&nbsp;抛出异常</a>
                    
                    
                    
                    <a class="navbar-item" href="#异常屏蔽">3.3&nbsp;&nbsp;异常屏蔽</a>
                    
                    
                    
                    <a class="navbar-item" href="#提问时贴出异常">3.4&nbsp;&nbsp;提问时贴出异常</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#自定义异常">4&nbsp;&nbsp;<b>自定义异常</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#NullPointerException">5&nbsp;&nbsp;<b>NullPointerException</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#处理NullPointerException">5.1&nbsp;&nbsp;处理NullPointerException</a>
                    
                    
                    
                    <a class="navbar-item" href="#定位NullPointerException">5.2&nbsp;&nbsp;定位NullPointerException</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用断言">6&nbsp;&nbsp;<b>使用断言</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用JDK-Logging">7&nbsp;&nbsp;<b>使用JDK Logging</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Commons-Logging">8&nbsp;&nbsp;<b>使用Commons Logging</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Log4j">9&nbsp;&nbsp;<b>使用Log4j</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用SLF4J和Logback">10&nbsp;&nbsp;<b>使用SLF4J和Logback</b></a>
                    
                </div>
            </div>
            
            
            <a class="navbar-item" title="GitHub" target="_blank" rel="noopener" href="https://github.com/wanzixin">
                
                <i class="fab fa-github"></i>
                
            </a>
               
            
        </div>
    </div>
</nav>

    <section class="section">
    <div class="container">
    <article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        
            异常处理
        
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            
                <span>Apr 27 2021</span>
            
        </span>
        
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <a class="article-category-link" href="/categories/Study/">Study</a><span>></span><a class="article-category-link" href="/categories/Study/Java/">Java</a>
        </span>
        
        
        <span class="column is-narrow">
            
            
            40 minutes read (About 5997 words)
        </span>
        
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
    
        <html><head></head><body><p>程序运行的时候，经常会发生各种错误。本章我们讨论如何在Java程序中处理各种异常情况。<span id="more"></span></p>
<h2 id="Java的异常"><a href="#Java的异常" class="headerlink" title="Java的异常"></a>Java的异常</h2><p>一个健壮的程序必须能处理各种各样的错误。所谓错误，就是程序调用某个函数的时候，如果失败了就代表出错了。调用方如何获知调用失败的信息？有两种方法：</p>
<p>方法一：约定返回错误码。因为使用int类型的错误码，想要处理就非常麻烦。这种方法常见于底层C函数。</p>
<p>方法二：在语言层面提供一个异常处理机制。Java内置了一套异常处理机制，总是使用异常来表示错误。异常是一种class，因此它本身带有类型信息。异常可以在任何地方抛出，但只需要在上层捕获，这样就和方法调用分离了。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">try</span> {<br>    String s = processFile(“C:\\test.txt”);<br>    <span class="hljs-comment">// ok:</span><br>} <span class="hljs-keyword">catch</span> (FileNotFoundException e) {<br>    <span class="hljs-comment">// file not found:</span><br>} <span class="hljs-keyword">catch</span> (SecurityException e) {<br>    <span class="hljs-comment">// no read permission:</span><br>} <span class="hljs-keyword">catch</span> (IOException e) {<br>    <span class="hljs-comment">// io error:</span><br>} <span class="hljs-keyword">catch</span> (Exception e) {<br>    <span class="hljs-comment">// other error:</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>从继承关系可知，<code>Throwable</code>是异常体系的根，它继承自Object。Throwable有两个体系：<code>Error</code>和<code>Exception</code>，Error表示严重的错误，程序对此一般无能为力，比如：</p>
<ul>
<li>OutOfMemoryError：内存耗尽</li>
<li>NoClassDefFoundError：无法加载某个类</li>
<li>StackOverflowError：栈溢出</li>
</ul>
<p>而Exception则是运行时的错误，它可以被捕获并处理。某些异常是应用程序逻辑处理的一部分，应该捕获并处理，例如：</p>
<ul>
<li>NumberFormatException：数值类型的格式错误</li>
<li>FileNotFoundException：未找到文件</li>
<li>SocketException：读取网络失败</li>
</ul>
<p>还有一些异常是程序逻辑编写不对造成的，应该修复程序本身。例如：</p>
<ul>
<li>NullPointerException：对某个null对象调用方法或字段</li>
<li>IndexOutOfBoundsException：数组索引越界</li>
</ul>
<p>Exception又分为两大类：RuntimeException以及它的子类，非RuntimeException（包括IOException、ReflectiveOperationException等等）。</p>
<p>Java规定：</p>
<ul>
<li>必须捕获的异常，包括Exception及其子类，但不包括RuntimeException及其子类，这种类型的异常称为Checked Exception。</li>
<li>不需要捕获的异常，包括Error及其子类，RuntimeException及其子类。</li>
</ul>
<h2 id="捕获异常"><a href="#捕获异常" class="headerlink" title="捕获异常"></a>捕获异常</h2><p>捕获异常使用<code>try...catch</code>语句，把可能发生异常的代码放到<code>try{...}</code>中，然后使用<code>catch</code>捕获对应的Exception及其子类。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        <span class="hljs-keyword">byte</span>[] bs = toGBK(<span class="hljs-string">"中文"</span>);<br>        System.out.println(Arrays.toString(bs));<br>    }<br><br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">byte</span>[] toGBK(String s) {<br>        <span class="hljs-keyword">try</span> {<br>            <span class="hljs-comment">// 用指定编码转换String为byte[]:</span><br>            <span class="hljs-keyword">return</span> s.getBytes(<span class="hljs-string">"GBK"</span>);<br>        } <span class="hljs-keyword">catch</span> (UnsupportedEncodingException e) {<br>            <span class="hljs-comment">// 如果系统不支持GBK编码，会捕获到UnsupportedEncodingException:</span><br>            System.out.println(e); <span class="hljs-comment">// 打印异常信息</span><br>            <span class="hljs-keyword">return</span> s.getBytes(); <span class="hljs-comment">// 尝试使用用默认编码</span><br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果我们不捕获<code>UnsupportEncodingException</code>，编译器会报错，报错信息类似于：unreported exception UnsupportedEncodingException; must be caught or declared to be thrown，并且准确地指出需要捕获的语句是<code>return s.getBytes("GBK");</code>。意思是说，像<code>UnsupportedEncodingException</code>这样的Checked Exception，必须被捕获。</p>
<p>这是因为String.getBytes(String)方法定义是：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">byte</span>[] getBytes(String charsetName) <span class="hljs-keyword">throws</span> UnsupportedEncodingException {<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>于是我们知道，在方法定义的时候，使用<code>throw xxx</code>表示该方法可能抛出的异常类型。调用方在调用时，必须强制捕获这些异常，否则编译器会报错。</p>
<p>在toGBK()方法中，因为调用了String.getBytes(String)方法，就必须捕获UnsupportedEncodingException。我们也可以不捕获它，而是在方法定义处使用throws表示toGBK()方法可能会抛出UnsupportedEncodingException，就可以让toGBK()方法通过编译器检查。</p>
<p>只要是方法声明的Checked Exception，不在调用层捕获，也必须在更高的调用层捕获。所有未捕获的异常，最终也必须在main()方法中捕获，这也是最后捕获Exception的机会。</p>
<blockquote>
<p>如果是测试代码，上面的写法就略嫌麻烦。如果不想写任何try代码，可以直接把main()方法定义为throws Exception。因为main()方法声明了可能抛出Exception，也就声明了可能抛出的所有Exception，因此在内部就无需捕获了。代价就是一旦发生异常，程序会立刻退出。</p>
</blockquote>
<p>还有一些同学喜欢捕获异常后不处理，这种方式是非常不好的，即使什么也做不了，也要把异常都记下来。所有异常都可以调用<code>printStackTrace()</code>方法打印异常栈，这是一个简单快速打印异常栈的方法。</p>
<h3 id="多catch语句"><a href="#多catch语句" class="headerlink" title="多catch语句"></a>多catch语句</h3><p>可以使用多个catch语句，每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后，会从上到下匹配catch语句，匹配到某个catch后，执行catch代码块，然后<strong>不在</strong>继续匹配。简单地说就是，多个catch语句只有一个能被执行。</p>
<p>因此，存在多个catch语句时，catch的顺序非常重要，子类必须写在前面。</p>
<h3 id="finally语句"><a href="#finally语句" class="headerlink" title="finally语句"></a>finally语句</h3><p>无论是否有异常发生，我们都希望执行一些语句，例如清理工作，怎么写？</p>
<p>Java的try…catch机制还提供了<code>finally</code>语句，finally语句块保证有无错误都会执行。</p>
<p>finally有以下几个特点：</p>
<ul>
<li>finally语句不是必须的</li>
<li>finally总是最后执行</li>
</ul>
<p>如果没有发生异常，就正常执行try语句块，然后执行finally。如果发生了异常，就中断执行try语句块，然后跳转执行匹配的catch语句块，最后执行finally。可见，finally用来保证一些代码必须执行。</p>
<p>某些情况下，例如方法声明了可能抛出的异常，可以没有catch，只使用try…finally结构。</p>
<h3 id="捕获多种异常"><a href="#捕获多种异常" class="headerlink" title="捕获多种异常"></a>捕获多种异常</h3><p>如果某些异常的处理逻辑相同，但是异常本身不存在继承关系，那么就得编写多条catch子句。但也可以用<code>|</code>把处理逻辑相同的异常合并到一起，就像这样。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>    <span class="hljs-keyword">try</span> {<br>        process1();<br>        process2();<br>        process3();<br>    } <span class="hljs-keyword">catch</span> (IOException | NumberFormatException e) { <span class="hljs-comment">// IOException或NumberFormatException</span><br>        System.out.println(<span class="hljs-string">"Bad input"</span>);<br>    } <span class="hljs-keyword">catch</span> (Exception e) {<br>        System.out.println(<span class="hljs-string">"Unknown error"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<h2 id="抛出异常"><a href="#抛出异常" class="headerlink" title="抛出异常"></a>抛出异常</h2><h3 id="异常的传播"><a href="#异常的传播" class="headerlink" title="异常的传播"></a>异常的传播</h3><p>当某个方法抛出了异常时，如果当前方法没有捕获异常，异常就会被抛到上层调用方法，直到遇到某个try…catch被捕获为止。</p>
<h3 id="抛出异常-1"><a href="#抛出异常-1" class="headerlink" title="抛出异常"></a>抛出异常</h3><p>当发生错误时，例如，用户输入了非法的字符，我们就可以抛出异常。如何抛出异常？分两步：</p>
<ol>
<li>创建某个Exception类</li>
<li>用throw语句抛出</li>
</ol>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">process2</span><span class="hljs-params">(String s)</span> </span>{<br>    <span class="hljs-keyword">if</span> (s==<span class="hljs-keyword">null</span>) {<br>        NullPointerException e = <span class="hljs-keyword">new</span> NullPointerException();<br>        <span class="hljs-keyword">throw</span> e;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>实际上，绝大部分抛出异常的代码都会合并写到一行：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">process2</span><span class="hljs-params">(String s)</span> </span>{<br>    <span class="hljs-keyword">if</span> (s==<span class="hljs-keyword">null</span>) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NullPointerException();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果一个方法捕获了某个异常后，又在catch子句中抛出新的异常，就相当于把抛出的异常类型“转换”了。</p>
<p>为了能追踪到完整的异常栈，在构造异常时，把原始的Exception实例传进去，新的Exception就可以持有原始Exception信息。</p>
<p>有了完整的异常栈的信息，我们才能快速定位并修复代码的问题。捕获到异常并再次抛出时，一定要留住原始异常，否则很难定位第一案发现场！</p>
<p>在代码中获取原始异常可以使用<code>Throwable.getCause()</code>方法，如果返回null，说明已经是“根异常”了。</p>
<h3 id="异常屏蔽"><a href="#异常屏蔽" class="headerlink" title="异常屏蔽"></a>异常屏蔽</h3><p>如果在执行finally语句时抛出异常，那么catch语句的异常还能否继续抛出？finally抛出异常后，原来在catch中准备抛出的异常就“消失”了，因为只能抛出一个异常，没有被抛出的异常称为“被屏蔽”的异常（Suppressed Exception）。</p>
<p>在极少数情况下，我们需要获知所有的异常。如何保存所有的异常信息？方法是先用origin变量保存原始异常，然后调用<code>Throwable.addSuppressed()</code>，把原始异常添加进来，最后在finally抛出。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>        Exception origin = <span class="hljs-keyword">null</span>;<br>        <span class="hljs-keyword">try</span> {<br>            System.out.println(Integer.parseInt(<span class="hljs-string">"abc"</span>));<br>        } <span class="hljs-keyword">catch</span> (Exception e) {<br>            origin = e;<br>            <span class="hljs-keyword">throw</span> e;<br>        } <span class="hljs-keyword">finally</span> {<br>            Exception e = <span class="hljs-keyword">new</span> IllegalArgumentException();<br>            <span class="hljs-keyword">if</span> (origin != <span class="hljs-keyword">null</span>) {<br>                e.addSuppressed(origin);<br>            }<br>            <span class="hljs-keyword">throw</span> e;<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>当catch和finally都抛出了异常时，虽然catch的异常被屏蔽了，但是finally抛出的异常仍然包含了它。</p>
<p>绝大多数情况下，在finally中不要抛出异常，通常不需要关心<code>Suppressed Exception</code>。</p>
<h3 id="提问时贴出异常"><a href="#提问时贴出异常" class="headerlink" title="提问时贴出异常"></a>提问时贴出异常</h3><p>异常打印的栈信息是找出问题的关键，许多初学者提问时只贴代码不贴异常，相当于只报案不给线索，福尔摩斯也无能为力。还有同学只贴部分异常信息，最关键的<code>Caused by: xxx</code>给省略了，这都属于不正确的提问方式，得改。</p>
<h2 id="自定义异常"><a href="#自定义异常" class="headerlink" title="自定义异常"></a>自定义异常</h2><p>Java标准库定义的常用异常包括：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs ascii">Exception<br>│<br>├─ RuntimeException<br>│  │<br>│  ├─ NullPointerException<br>│  │<br>│  ├─ IndexOutOfBoundsException<br>│  │<br>│  ├─ SecurityException<br>│  │<br>│  └─ IllegalArgumentException<br>│     │<br>│     └─ NumberFormatException<br>│<br>├─ IOException<br>│  │<br>│  ├─ UnsupportedCharsetException<br>│  │<br>│  ├─ FileNotFoundException<br>│  │<br>│  └─ SocketException<br>│<br>├─ ParseException<br>│<br>├─ GeneralSecurityException<br>│<br>├─ SQLException<br>│<br>└─ TimeoutException<br></code></pre></td></tr></tbody></table></figure>

<p>当我们在代码中需要抛出异常时，尽量使用JDK已定义的异常类型。</p>
<p>在一个大型项目中，可以自定义新的异常类型，但是保持一个合理的异常继承体系是非常重要的。</p>
<p>一个常见的做法是自定义一个<code>BaseException</code>作为根异常，然后派生出各种业务类型的异常。BaseExcption需要从一个合适的Exception派生，通常建议从<code>RuntimeException</code>派生。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BaseException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span></span>{<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>其他业务类型的异常就可以从BaseException派生。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserNotFoundException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseException</span> </span>{<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginFailedException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseException</span> </span>{<br>}<br><br>...<br></code></pre></td></tr></tbody></table></figure>

<p>自定义的BaseException应该提供多个构造方法。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BaseException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BaseException</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">super</span>();<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BaseException</span><span class="hljs-params">(String message, Throwable cause)</span> </span>{<br>        <span class="hljs-keyword">super</span>(message, cause);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BaseException</span><span class="hljs-params">(String message)</span> </span>{<br>        <span class="hljs-keyword">super</span>(message);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BaseException</span><span class="hljs-params">(Throwable cause)</span> </span>{<br>        <span class="hljs-keyword">super</span>(cause);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样，抛出异常的时候，就可以选择合适的构造方法。</p>
<h2 id="NullPointerException"><a href="#NullPointerException" class="headerlink" title="NullPointerException"></a>NullPointerException</h2><p>在所有的RuntimeException异常中，Java程序员最熟悉的恐怕就是NullPointerException了。NullPointerException俗称NPE，即空指针异常。如果一个对象是null，调用其方法或访问其字段就会产生NullPointerException，这个异常通常是由JVM抛出的。</p>
<p>指针这个概念实际上源自C语言，Java语言中并无指针。我们定义的变量实际上是引用，Null Pointer更确切地说是Null Reference，不过两者区别不大。</p>
<h3 id="处理NullPointerException"><a href="#处理NullPointerException" class="headerlink" title="处理NullPointerException"></a>处理NullPointerException</h3><p>首先，必须明确，NullPointerException是一种代码逻辑错误，遇到NullPointerException，遵循原则是早暴露，早修复，严禁使用catch来隐藏这种编码错误。</p>
<p>好的编码习惯可以极大地降低NullPointerException的产生，例如，成员变量在定义时初始化：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    <span class="hljs-keyword">private</span> String name = <span class="hljs-string">""</span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>使用空字符串<code>""</code>而不是默认的<code>null</code>可避免很多<code>NullPointerException</code>，编写业务逻辑时，用空字符串<code>""</code>表示未填写比<code>null</code>安全得多。</p>
<h3 id="定位NullPointerException"><a href="#定位NullPointerException" class="headerlink" title="定位NullPointerException"></a>定位NullPointerException</h3><p>如果产生了NullPointerException，例如，调用<code>a.b.c.x()</code>时产生了<code>NullPointerException</code>，原因可能是：</p>
<ul>
<li>a是null</li>
<li>a.b是null</li>
<li>a.b.c是null</li>
</ul>
<p>确定到底是哪个对象是<code>null</code>以前只能打印这样的日志：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">System.out.println(a);<br>System.out.println(a.b);<br>System.out.println(a.b.c);<br></code></pre></td></tr></tbody></table></figure>

<p>从Java 14开始，如果产生了NullPointerException，JVM可以给出详细的信息告诉我们<code>null</code>对象到底是谁。</p>
<h2 id="使用断言"><a href="#使用断言" class="headerlink" title="使用断言"></a>使用断言</h2><p>断言（Assertion）是一种调试程序的方式。在Java中，使用assert关键字来实现断言。先看一个栗子：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>    <span class="hljs-keyword">double</span> x = Math.abs(-<span class="hljs-number">123.45</span>);<br>    <span class="hljs-keyword">assert</span> x &gt;= <span class="hljs-number">0</span>;<br>    System.out.println(x);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>语句<code>assert x &gt;= 0;</code>即为断言，断言条件<code>x &gt;= 0</code>预期为<code>true</code>。如果计算结果为<code>false</code>，则断言失败，抛出<code>AssertionError</code>。</p>
<p>使用<code>assert</code>语句时，还可以添加一个可选的断言消息：<code>assert x &gt;= 0 : "x must &gt;= 0";</code>。这样，断言失败的时候，<code>AssertionError</code>会带上消息<code>x must &gt;= 0</code>，更加便于调试。</p>
<p>Java断言的特点是：断言失败时会抛出AssertionError，导致程序结束退出。因此，断言不能用于可恢复的程序错误，只应用于开发和测试阶段。</p>
<p>JVM默认关闭断言指令，即遇到assert语句就自动忽略了。要执行assert语句，必须给Java虚拟机传递<code>-enableassertions</code>（简写为-ea）参数启用断言。</p>
<p>还可以有选择地对特定地类启用断言，命令行参数是：<code>-ea:com.itranswarp.sample.Main</code>，表示只对<code>com.itranswarp.sample.Main</code>这个类启用断言。</p>
<p>或者对特定地包启用断言，命令行参数是：<code>-ea:com.itranswarp.sample...</code>（注意结尾有3个<code>.</code>），表示对<code>com.itranswarp.sample</code>这个包启动断言。</p>
<p>实际开发中，很少使用断言。更好的方法是编写单元测试，后续我们会讲解<code>JUnit</code>的使用。</p>
<h2 id="使用JDK-Logging"><a href="#使用JDK-Logging" class="headerlink" title="使用JDK Logging"></a>使用JDK Logging</h2><p>什么是日志？日志就是Logging，它的目的是为了取代System.out.println()。输出日志，而不是用System.out.println()，有以下几个好处：</p>
<ul>
<li>可以设置输出样式，避免每次都写<code>"Error: "+var</code></li>
<li>可以设置输出级别，禁止某些级别输出。例如，只输出错误日志。</li>
<li>可以被重定向到文件，这样可以在程序运行结束后查看日志</li>
<li>可以按包名控制日志级别，只输出某些包打的日志</li>
<li>…</li>
</ul>
<p>Java标准库内置了日志包<code>java.util.logging</code>，我们可以直接用。使用日志最大的好处是，它自动打印了时间、调用类、调用方法等很多有用的信息。</p>
<p>JDK的Logging定义了七个日志级别，从严重到普通：</p>
<ul>
<li>SEVERE</li>
<li>WARNING</li>
<li>INFO</li>
<li>CONFIG</li>
<li>FINE</li>
<li>FINER</li>
<li>FINEST</li>
</ul>
<p>默认级别是INFO，因此INFO级别以下的日志不会被打印出来。使用日志级别的好处在于，调整级别就可以屏蔽掉很多调试相关的日志输出。</p>
<p>使用Java标准库内置的Logging有以下局限：</p>
<ul>
<li>Logging系统在JVM启动时读取配置文件并完成初始化，一旦开始运行main()方法，就无法修改配置</li>
<li>配置不太方便，需要在JVM启动时传递参数<code>-Djava.util.logging.config.file=&lt;config-file-name&gt;</code>。</li>
</ul>
<p>因此，Java标准库内置的Logging使用并不是很广泛。更方便的日志系统我们稍后介绍。</p>
<h2 id="使用Commons-Logging"><a href="#使用Commons-Logging" class="headerlink" title="使用Commons Logging"></a>使用Commons Logging</h2><p>和Java标准库提供的日志不同，Commons Logging是一个第三方日志库，它是由Apache创建的日志模块。</p>
<p>Commons Logging的特点是，它可以通过配置文件指定挂接的日志系统。默认情况下，Commons Logging自动搜索并使用Log4j（Log4j是另一个流行的日志系统），如果没有找到Log4j，再使用JDK Logging。</p>
<p>使用Commons Logging只需要和两个类打交道，并且只有两步：</p>
<ol>
<li>通过<code>LogFactory</code>获取<code>Log</code>类的实例</li>
<li>使用<code>Log</code>实例的方法打日志</li>
</ol>
<p>Commons Logging定义了6个日志级别：</p>
<ul>
<li>FATAL</li>
<li>ERROR</li>
<li>WARNING</li>
<li>INFO</li>
<li>DEBUG</li>
<li>TRACE</li>
</ul>
<p>默认级别是INFO。</p>
<p>使用Commons Logging时，如果在静态方法中引用Log，通常直接定义一个静态变量。在实例方法中引用Log，通常定义一个实例变量。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 在静态方法中引用Log:</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{<br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Log log = LogFactory.getLog(Main.class);<br>    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span> </span>{<br>        log.info(<span class="hljs-string">"foo"</span>);<br>    }<br>}<br><br><span class="hljs-comment">// 在实例方法中引用Log:</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{<br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">final</span> Log log = LogFactory.getLog(getClass());<br>    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span> </span>{<br>        log.info(<span class="hljs-string">"foo"</span>);<br>    }<br>}<br><br><span class="hljs-comment">// 在子类中使用父类实例化的log:</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Person</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span> </span>{<br>        log.info(<span class="hljs-string">"bar"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到实例变量log的获取方式是<code>LogFactory.getLog(getClass())</code>，虽然也可以用<code>LogFactory.getLog(Person.class)</code>，但是前一种方式有个非常大的好处，就是子类可以直接使用该<code>log</code>实例。</p>
<p>由于Java类的动态特性，子类获取的<code>log</code>字段实际上相当于<code>LogFactory.getLog(Student.class)</code>，但却是从父类继承而来，并且无需改动代码。</p>
<p>此外，Commons Logging的日志方法，例如info()，除了标准的<code>info(String)</code>，还提供了一个非常有用的重载方法：<code>info(String, Throwable)</code>，这使得记录异常更加简单。</p>
<h2 id="使用Log4j"><a href="#使用Log4j" class="headerlink" title="使用Log4j"></a>使用Log4j</h2><p>前面介绍了Commons Logging，可以作为“日志接口”来使用，而真正的“日志实现”可以使用Log4j。Log4j是一种非常流行的日志框架。Log4j是一个组件化设计的日志系统，它的架构大致如下：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs ascii">log.info("User signed in.");<br> │<br> │   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐<br> ├──&gt;│ Appender │───&gt;│  Filter  │───&gt;│  Layout  │───&gt;│ Console  │<br> │   └──────────┘    └──────────┘    └──────────┘    └──────────┘<br> │<br> │   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐<br> ├──&gt;│ Appender │───&gt;│  Filter  │───&gt;│  Layout  │───&gt;│   File   │<br> │   └──────────┘    └──────────┘    └──────────┘    └──────────┘<br> │<br> │   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐<br> └──&gt;│ Appender │───&gt;│  Filter  │───&gt;│  Layout  │───&gt;│  Socket  │<br>     └──────────┘    └──────────┘    └──────────┘    └──────────┘<br></code></pre></td></tr></tbody></table></figure>

<p>当我们用Log4j输出一条日志时，Log4j自动通过不同的Appender把同一条日志输出到不同的目的地。例如：</p>
<ul>
<li>console：输出到屏幕</li>
<li>file：输出到文件</li>
<li>socket：通过网络输出到远程计算机</li>
<li>jdbc：输出到数据库</li>
</ul>
<p>在输出日志的过程中，通过Filter来过滤哪些log需要被输出，哪些log不需要被输出。例如，仅输出ERROR级别的日志。</p>
<p>最后通过Layout来格式化日志信息，例如，自动添加日期、时间、方法名称等。</p>
<p>上述结构虽然复杂，但我们在实际使用时，并不需要关心Log4j的API，而是通过配置文件来配置它。以XML配置为例，使用Log4j时，我们把一个log4j2.xml文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志。下面是一个配置文件的例子。</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">Configuration</span>&gt;</span><br>	<span class="hljs-tag">&lt;<span class="hljs-name">Properties</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- 定义日志格式 --&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">Property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"log.pattern"</span>&gt;</span>%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n<span class="hljs-tag">&lt;/<span class="hljs-name">Property</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- 定义文件名变量 --&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">Property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file.err.filename"</span>&gt;</span>log/err.log<span class="hljs-tag">&lt;/<span class="hljs-name">Property</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">Property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file.err.pattern"</span>&gt;</span>log/err.%i.log.gz<span class="hljs-tag">&lt;/<span class="hljs-name">Property</span>&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">Properties</span>&gt;</span><br>    <span class="hljs-comment">&lt;!-- 定义Appender，即目的地 --&gt;</span><br>	<span class="hljs-tag">&lt;<span class="hljs-name">Appenders</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- 定义输出到屏幕 --&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">Console</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"console"</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"SYSTEM_OUT"</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- 日志格式引用上面定义的log.pattern --&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">PatternLayout</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"${log.pattern}"</span> /&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">Console</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- 定义输出到文件,文件名引用上面定义的file.err.filename --&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">RollingFile</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"err"</span> <span class="hljs-attr">bufferedIO</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">fileName</span>=<span class="hljs-string">"${file.err.filename}"</span> <span class="hljs-attr">filePattern</span>=<span class="hljs-string">"${file.err.pattern}"</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">PatternLayout</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"${log.pattern}"</span> /&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">Policies</span>&gt;</span><br>                <span class="hljs-comment">&lt;!-- 根据文件大小自动切割日志 --&gt;</span><br>				<span class="hljs-tag">&lt;<span class="hljs-name">SizeBasedTriggeringPolicy</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"1 MB"</span> /&gt;</span><br>			<span class="hljs-tag">&lt;/<span class="hljs-name">Policies</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- 保留最近10份 --&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">DefaultRolloverStrategy</span> <span class="hljs-attr">max</span>=<span class="hljs-string">"10"</span> /&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">RollingFile</span>&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">Appenders</span>&gt;</span><br>	<span class="hljs-tag">&lt;<span class="hljs-name">Loggers</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">Root</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"info"</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- 对info级别的日志，输出到console --&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">AppenderRef</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"console"</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"info"</span> /&gt;</span><br>            <span class="hljs-comment">&lt;!-- 对error级别的日志，输出到err，即上面定义的RollingFile --&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">AppenderRef</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"err"</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"error"</span> /&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">Root</span>&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">Loggers</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">Configuration</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>虽然配置Log4j比较繁琐，但一旦配置完成，使用起来就非常方便。对上面的配置文件，凡是<code>INFO</code>级别的日志，会自动输出到屏幕，而<code>ERROR</code>级别的日志，不但会输出到屏幕，还会同时输出到文件。并且，一旦日志文件达到指定大小（1MB），Log4j就会自动切割新的日志文件，并最多保留10份。</p>
<p>在开发阶段，始终使用Commons Logging接口来写入日志，并且开发阶段无需引入Log4j。如果需要把日志写入文件， 只需要把正确的配置文件和Log4j相关的jar包放入<code>classpath</code>，就可以自动把日志切换成使用Log4j写入，无需修改任何代码。</p>
<h2 id="使用SLF4J和Logback"><a href="#使用SLF4J和Logback" class="headerlink" title="使用SLF4J和Logback"></a>使用SLF4J和Logback</h2><p>前面介绍了Commons Logging和Log4j这一对好基友，一个负责充当日志API，一个负责实现日志底层，搭配使用非常方便开发。有的童鞋可能还听说过SLF4J和Logback。SLF4J类似于Commons Logging，也是一个日志接口，而Logback类似于Log4j，是一个日志的实现。</p>
<p>为什么有了Commons Logging和Log4j，又会蹦出来SLF4J和Logback？这是因为Java有着非常悠久的开源历史，不但OpenJDK本身是开源的，而且我们用到的第三方库，几乎全部都是开源的。开源生态丰富的一个特定就是，同一个功能，可以找到若干种互相竞争的开源库。因为对Commons Logging的接口不满意，有人就搞了SLF4J。因为对Log4j的性能不满意，有人就搞了Logback。</p>
<p>我们先来看看SLF4J对Commons Logging的接口有何改进。在Commons Logging中，我们要打印日志，有时候得这么写：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> score = <span class="hljs-number">99</span>;<br>p.setScore(score);<br>log.info(<span class="hljs-string">"Set score "</span> + score + <span class="hljs-string">" for Person "</span> + p.getName() + <span class="hljs-string">" ok."</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>拼字符串是一个非常麻烦的事，所以SLF4J的日志接口改成了这样：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">int</span> score = <span class="hljs-number">99</span>;<br>p.setScore(score);<br>logger.info(<span class="hljs-string">"Set score {} for Person {} ok."</span>, score, p.getName());<br></code></pre></td></tr></tbody></table></figure>

<p>我们靠猜也能猜出来，SLF4J的日志接口传入的是一个带占位符的字符串，用后面的变量自动替换占位符，看起来更自然。</p>
<p>如何使用SLF4J呢？它的接口实际上和Commons Logging几乎一模一样。对比一下二者的接口。</p>
<table>
<thead>
<tr>
<th align="left">Commons Logging</th>
<th align="left">SLF4J</th>
</tr>
</thead>
<tbody><tr>
<td align="left">org.apache.commons.logging.Log</td>
<td align="left">org.slf4j.Logger</td>
</tr>
<tr>
<td align="left">org.apache.commons.logging.LogFactory</td>
<td align="left">org.slf4j.LoggerFactory</td>
</tr>
</tbody></table>
<p>和Log4j类似，我们仍然需要一个Logback的配置文件，把<code>logback.xml</code>放到classpath下，配置如下：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br><br>	<span class="hljs-tag">&lt;<span class="hljs-name">appender</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"CONSOLE"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.ConsoleAppender"</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">encoder</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">pattern</span>&gt;</span>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n<span class="hljs-tag">&lt;/<span class="hljs-name">pattern</span>&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">encoder</span>&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">appender</span>&gt;</span><br><br>	<span class="hljs-tag">&lt;<span class="hljs-name">appender</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FILE"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.RollingFileAppender"</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">encoder</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">pattern</span>&gt;</span>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n<span class="hljs-tag">&lt;/<span class="hljs-name">pattern</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">charset</span>&gt;</span>utf-8<span class="hljs-tag">&lt;/<span class="hljs-name">charset</span>&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">encoder</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">file</span>&gt;</span>log/output.log<span class="hljs-tag">&lt;/<span class="hljs-name">file</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">rollingPolicy</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.FixedWindowRollingPolicy"</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">fileNamePattern</span>&gt;</span>log/output.log.%i<span class="hljs-tag">&lt;/<span class="hljs-name">fileNamePattern</span>&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">rollingPolicy</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">triggeringPolicy</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"</span>&gt;</span><br>			<span class="hljs-tag">&lt;<span class="hljs-name">MaxFileSize</span>&gt;</span>1MB<span class="hljs-tag">&lt;/<span class="hljs-name">MaxFileSize</span>&gt;</span><br>		<span class="hljs-tag">&lt;/<span class="hljs-name">triggeringPolicy</span>&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">appender</span>&gt;</span><br><br>	<span class="hljs-tag">&lt;<span class="hljs-name">root</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"INFO"</span>&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">appender-ref</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"CONSOLE"</span> /&gt;</span><br>		<span class="hljs-tag">&lt;<span class="hljs-name">appender-ref</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"FILE"</span> /&gt;</span><br>	<span class="hljs-tag">&lt;/<span class="hljs-name">root</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>从目前的趋势来看，越来越多的开源项目从Commons Logging加Log4j转向了SLF4J加Logback。</p>
</body></html>
    
    </div>
    
    
    <div class="columns is-mobile is-multiline article-nav">
        <span class="column is-12-mobile is-half-desktop  article-nav-prev">
            
            <a href="/Study/Java/%E5%8F%8D%E5%B0%84/">反射</a>
            
        </span>
        <span class="column is-12-mobile is-half-desktop  article-nav-next">
            
            <a href="/Study/Java/Java%E6%A0%B8%E5%BF%83%E7%B1%BB/">Java核心类</a>
            
        </span>
    </div>
    
</article>


<div class="sharebox">
    
<div class="sharethis-inline-share-buttons"></div>
<script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=608c1408daac690012507aa2&amp;product=sop' async='async'></script>

</div>



    </div>
</section>
    <footer class="footer">
    <div class="container">
        <div class="columns content">
            <div class="column is-narrow has-text-centered">
                &copy; 2021 wanzixin&nbsp;
                Powered by <a href="http://hexo.io/" target="_blank">Hexo</a> & <a
                        target="_blank" rel="noopener" href="http://github.com/ppoffice/hexo-theme-minos">Minos</a>
            </div>
            <div class="column is-hidden-mobile"></div>

            
            <div class="column is-narrow">
                <div class="columns is-mobile is-multiline is-centered">
                
                    
                <a class="column is-narrow has-text-black" title="GitHub" target="_blank" rel="noopener" href="https://github.com/ppoffice/hexo-theme-minos">
                    
                    GitHub
                    
                </a>
                
                </div>
            </div>
            
            
<div class="column is-narrow has-text-centered">
    <div class="dropdown is-up is-right is-hoverable" style="margin-top: -0.2em;">
        <div class="dropdown-trigger">
            <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu7">
                <span class="icon">
                    <i class="fas fa-globe"></i>
                </span>
                <span>English</span>
                <span class="icon is-small">
            <i class="fas fa-angle-down" aria-hidden="true"></i>
          </span>
            </button>
        </div>
        <div class="dropdown-menu has-text-left" role="menu">
            <div class="dropdown-content">
            
                <a href="/Study/Java/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/" class="dropdown-item">
                    English
                </a>
            
                <a href="/zh-cn/Study/Java/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/" class="dropdown-item">
                    简体中文
                </a>
            
            </div>
        </div>
    </div>
</div>

        </div>
    </div>
</footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>

<!-- test if the browser is outdated -->
<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" target="_blank" rel="noopener" href="http://outdatedbrowser.com/">Update my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js"></script>
<script>
    $(document).ready(function () {
        // plugin function, place inside DOM ready function
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        })
    });
</script>

<script>
    window.FontAwesomeConfig = {
        searchPseudoElements: true
    }
    moment.locale("en-AU");
</script>


    
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script>
    MathJax.Hub.Config({
        "HTML-CSS": {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
</script>

    
    
    
    
<script src="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery-all.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/js/jquery.justifiedGallery.min.js"></script>
<script>
    (function ($) {
        $(document).ready(function () {
            if (typeof($.fn.lightGallery) === 'function') {
                $('.article.gallery').lightGallery({ selector: '.gallery-item' });
            }
            if (typeof($.fn.justifiedGallery) === 'function') {
                $('.justified-gallery').justifiedGallery();
            }
        });
    })(jQuery);
</script>

    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
    <style>
        .hljs {
            position: relative;
        }

        .hljs .clipboard-btn {
            float: right;
            color: #9a9a9a;
            background: none;
            border: none;
            cursor: pointer;
        }

        .hljs .clipboard-btn:hover {
          color: #8a8a8a;
        }

        .hljs > .clipboard-btn {
            display: none;
            position: absolute;
            right: 4px;
            top: 4px;
        }

        .hljs:hover > .clipboard-btn {
            display: inline;
        }

        .hljs > figcaption > .clipboard-btn {
            margin-right: 4px;
        }
    </style>
    <script>
      $(document).ready(function () {
        $('figure.hljs').each(function(i, figure) {
          var codeId = 'code-' + i;
          var code = figure.querySelector('.code');
          var copyButton = $('<button>Copy <i class="far fa-clipboard"></i></button>');
          code.id = codeId;
          copyButton.addClass('clipboard-btn');
          copyButton.attr('data-clipboard-target-id', codeId);

          var figcaption = figure.querySelector('figcaption');

          if (figcaption) {
            figcaption.append(copyButton[0]);
          } else {
            figure.prepend(copyButton[0]);
          }
        })

        var clipboard = new ClipboardJS('.clipboard-btn', {
          target: function(trigger) {
            return document.getElementById(trigger.getAttribute('data-clipboard-target-id'));
          }
        });
        clipboard.on('success', function(e) {
          e.clearSelection();
        })
      })
    </script>

    
    

    



<script src="/js/script.js"></script>


    
    <div class="searchbox ins-search">
    <div class="searchbox-mask"></div>
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="Type something..." />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: 'Posts',
                PAGES: 'Pages',
                CATEGORIES: 'Categories',
                TAGS: 'Tags',
                UNTITLED: '(Untitled)',
            },
            CONTENT_URL: '/content.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>

<script src="/js/insight.js"></script>

    
</body>
</html>